package cn.stimd.spring.aop.framework;

import cn.stimd.spring.aop.ProxyMethodInvocation;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 通过反射的方式调用目标对象，子类可以重写invokeJoinpoint方法改变行为，因此该类也可以作为定制化实现MethodInvocation的基类
 * ReflectiveMethodInvocation封装了代理对象、目标对象及方法、拦截器链，将目标对象的方法置于拦截器链的执行过程中，以完成代理的具体逻辑
 * <p>
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
    protected final Object proxy;       //代理对象
    protected final Object target;      //目标对象
    private final Class<?> targetClass; //目标对象的类型
    protected final Method method;      //调用的方法
    protected Object[] arguments;       //方法参数
    protected final List<?> interceptorsAndDynamicMethodMatchers;   //拦截器链
    private int currentInterceptorIndex = -1;       //记录当前拦截器的下标

    protected ReflectiveMethodInvocation(Object proxy, Object target, Method method, Object[] arguments,
                                         Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = method;
        this.arguments = arguments;
        this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
    }


    /**
     * 递归调用，依次访问拦截器链
     */
    @Override
    public Object proceed() throws Throwable {
        //2. 当拦截器链的所有拦截器都得到执行，调用目标方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        //1. 执行拦截器链中的下一个拦截器
        Object advice = this.interceptorsAndDynamicMethodMatchers.get(++currentInterceptorIndex);
        return ((MethodInterceptor) advice).invoke(this);
    }


    /**
     * 默认使用反射调用，允许子类重写
     */
    protected Object invokeJoinpoint() throws Throwable {
        ReflectionUtils.makeAccessible(this.method);
        try {
            return this.method.invoke(this.target, this.arguments);
        }catch (InvocationTargetException ex){
            throw ex.getTargetException();      //抛出原始异常
        }
    }


    @Override
    public Method getMethod() {
        return this.method;
    }

    @Override
    public Object[] getArguments() {
        return this.arguments;
    }

    @Override
    public Object getThis() {
        return this.target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return this.method;
    }

    @Override
    public Object getProxy() {
        return this.proxy;
    }

    @Override
    public void setArguments(Object[] arguments) {
        this.arguments = arguments;
    }
}
